package com.jian.mall.service.impl;

import com.jian.mall.common.to.elasticsearch.SkuEsTo;
import com.jian.mall.config.ElasticsearchConfig;
import com.jian.mall.content.EsIndex;
import com.jian.mall.service.ElasticsearchService;
import com.jian.mall.vo.SearchConditionVo;
import com.jian.mall.vo.SearchResult;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.MultiBucketsAggregation;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.alibaba.fastjson.JSON;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;


@Service
@Slf4j
public class ElasticsearchServiceImpl implements ElasticsearchService {

    @Autowired
    RestHighLevelClient client;

    public static final int PageSize = 12;
    public Integer currentPageIndex = 0;

    @Override
    public void saveBatch(List<SkuEsTo> skuEsTo) throws IOException {
        BulkRequest request = new BulkRequest();
        for (SkuEsTo esTo : skuEsTo) {
            IndexRequest indexRequest = new IndexRequest(EsIndex.INDEX_PRODUCE);
            indexRequest.id(esTo.getSkuId().toString());
            indexRequest.source(JSON.toJSONString(esTo), XContentType.JSON);
            request.add(indexRequest);
        }
        BulkResponse bulk = client.bulk(request, ElasticsearchConfig.COMMON_OPTIONS);
        if (bulk.hasFailures()) {
            List<String> collect = Arrays.stream(bulk.getItems()).map(m -> m.getId()).collect(Collectors.toList());
            log.error("商品上架错误 {}", collect);
        }
    }

    @Override
    public SearchResult byConditionVoSearch(SearchConditionVo conditionVo) {
        SearchRequest request = new SearchRequest();
        request.indices("produce");
        SearchSourceBuilder builder = new SearchSourceBuilder();
        // 查询条件
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        if (StringUtils.isNotBlank(conditionVo.getKeyword()))
            boolQuery.must(QueryBuilders.matchQuery("skuTitle", conditionVo.getKeyword()));
        else
            boolQuery.must(QueryBuilders.matchAllQuery());

        List<QueryBuilder> filter = boolQuery.filter();
        if (conditionVo.getCatalog3Id() != null)
            filter.add(QueryBuilders.termQuery("catalogId", conditionVo.getCatalog3Id()));

        if (conditionVo.getBrandId() != null && conditionVo.getBrandId().size() != 0)
            filter.add(QueryBuilders.termsQuery("brandId", conditionVo.getBrandId()));

        if (conditionVo.getHasStock() != null)
            filter.add(QueryBuilders.termQuery("hasStock", conditionVo.getHasStock()));

        String rangePrice = conditionVo.getSkuPrice();
        if (StringUtils.isNotBlank(rangePrice)) {
            // 获取价格区间 _500  500_600 600_
            int i = rangePrice.indexOf("_");
            if (i == 0) {
                filter.add(QueryBuilders.rangeQuery("skuPrice").lte(rangePrice.substring(1)));
            } else if (i == conditionVo.getSkuPrice().length() - 1) {
                filter.add(QueryBuilders.rangeQuery("skuPrice").gte(rangePrice.substring(0, rangePrice.length() - 1)));
            } else if (i != -1) {
                filter.add(QueryBuilders.rangeQuery("skuPrice")
                        .gte(rangePrice.substring(0, i))
                        .lte(rangePrice.substring(i + 1)));
            }
        }

        // attrs的nested查询条件
        List<String> attrs = conditionVo.getAttrs();
        if (attrs != null && attrs.size() > 0) {
            attrs.forEach(a -> {
                // attrs=1_PGBM10&attrs="2_OPPO Reno8"
                int i = a.indexOf("_");
                if (i == -1) return;
                String split = a.substring(i + 1);
                if (split.length() > 0) {
                    BoolQueryBuilder attrNestedQuery = QueryBuilders.boolQuery();
                    attrNestedQuery.must(QueryBuilders.termQuery("attrs.attrId", a.substring(0, i)));
                    attrNestedQuery.must(QueryBuilders.termsQuery("attrs.attrValue", split));
                    filter.add(QueryBuilders.nestedQuery("attrs", attrNestedQuery, ScoreMode.None));
                }
            });
        }

        // 将查询条件放入构造器中
        builder.query(boolQuery);


        // 聚合条件
        TermsAggregationBuilder catalogAgg = AggregationBuilders.terms("catalogAgg").field("catalogId").size(10)
                .subAggregation(AggregationBuilders.terms("catalogNameAgg").field("catalogName").size(10));

        TermsAggregationBuilder brandAgg = AggregationBuilders.terms("brandAgg").field("brandId").size(10)
                .subAggregation(AggregationBuilders.terms("brandNameAgg").field("brandName").size(10))
                .subAggregation(AggregationBuilders.terms("brandImgAgg").field("brandImg").size(10));

        NestedAggregationBuilder attrAgg = AggregationBuilders.nested("attrAgg", "attrs")
                .subAggregation(AggregationBuilders.terms("attrIdAgg").field("attrs.attrId").size(10)
                        .subAggregation(AggregationBuilders.terms("attrNameAgg").field("attrs.attrName").size(10)
                                .subAggregation(AggregationBuilders.terms("attrValueAgg").field("attrs.attrValue").size(10))));
        // 将聚合条件放入构造器中
        builder.aggregation(catalogAgg);
        builder.aggregation(brandAgg);
        builder.aggregation(attrAgg);

        // 排序 分页 高亮
        // sort=skuPrice_desc
        String sort = conditionVo.getSort();
        if (StringUtils.isNotBlank(sort)) {
            int i = sort.indexOf("_");
            if (i != -1) {
                FieldSortBuilder orderBy = SortBuilders.fieldSort(sort.substring(0, i))
                        .order("desc".equals(sort.substring(i + 1)) ? SortOrder.DESC : SortOrder.ASC);
                builder.sort(orderBy);
            }
        }

        currentPageIndex = conditionVo.getPageNum();
        builder.from(currentPageIndex == null ? 0 : (currentPageIndex - 1) * PageSize);
        builder.size(PageSize);

        builder.highlighter(new HighlightBuilder().field("skuTitle").preTags("<b style='color:red'>").postTags("</b>"));

        System.out.println(builder);

        SearchResponse result = null;
        try {
            result = client.search(request.source(builder), ElasticsearchConfig.COMMON_OPTIONS);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return parseEStoSearchResult(result);
    }

    private SearchResult parseEStoSearchResult(SearchResponse response) {
        SearchResult result = new SearchResult();
        SearchHits hits = response.getHits();
        // 解析 hits
        SearchHit[] hitsHits = hits.getHits();
        List<SkuEsTo> list = Arrays.stream(hitsHits).map(h -> {
            SkuEsTo skuEsTo = JSON.parseObject(h.getSourceAsString(), SkuEsTo.class);
            try {
                String title = h.getHighlightFields().get("skuTitle").fragments()[0].string();
                skuEsTo.setSkuTitle(title);
            } catch (NullPointerException ignored) {
            }
            return skuEsTo;
        }).collect(Collectors.toList());

        result.setProducts(list);
        // 解析 页码等信息
        long pageTotalSize = hits.getTotalHits().value;
        result.setPageNum(currentPageIndex == null ? 1 : currentPageIndex);
        result.setTotal(pageTotalSize);
        result.setTotalPages(
                (int) (pageTotalSize % PageSize == 0 ?
                        pageTotalSize / PageSize : pageTotalSize / PageSize + 1)
        );
        ArrayList<Integer> pageList = new ArrayList<>();
        for (int i = 0; i < result.getTotalPages(); i++) {
            pageList.add(i + 1);
        }
        result.setPageList(pageList);

        // 解析聚合
        // 品牌
        Terms brandAgg = response.getAggregations().get("brandAgg");
        List<? extends Terms.Bucket> brandBuckets = brandAgg.getBuckets();
        List<SearchResult.BrandVo> brandVoList = brandBuckets.stream().map(m -> {
            SearchResult.BrandVo brandVo = new SearchResult.BrandVo();
            try {
                String brandId = m.getKeyAsString();
                brandVo.setBrandId(Long.valueOf(brandId));
                Terms brandNameAgg = m.getAggregations().get("brandNameAgg");
                brandVo.setBrandName(brandNameAgg.getBuckets().get(0).getKeyAsString());
                Terms brandImgAgg = m.getAggregations().get("brandImgAgg");
                brandVo.setBrandImg(brandImgAgg.getBuckets().get(0).getKeyAsString());

            } catch (NullPointerException e) {
                log.error(e.getMessage(), e);
            }
            return brandVo;
        }).collect(Collectors.toList());
        result.setBrands(brandVoList);

        // 分类
        Terms catalogAgg = response.getAggregations().get("catalogAgg");
        List<? extends Terms.Bucket> CatalogBuckets = catalogAgg.getBuckets();
        List<SearchResult.CatalogVo> catalogVoList = CatalogBuckets.stream().map(m -> {
            SearchResult.CatalogVo catalogVo = new SearchResult.CatalogVo();
            try {
                catalogVo.setCatalogId(Long.valueOf(m.getKeyAsString()));
                Terms catalogNameAgg = m.getAggregations().get("catalogNameAgg");
                catalogVo.setCatalogName(catalogNameAgg.getBuckets().get(0).getKeyAsString());
            } catch (NullPointerException e) {
                log.error(e.getMessage(), e);
            }
            return catalogVo;
        }).collect(Collectors.toList());
        result.setCatalogs(catalogVoList);

        // 属性
        ParsedNested attrAgg = response.getAggregations().get("attrAgg");
        Terms attrIdAgg = attrAgg.getAggregations().get("attrIdAgg");
        List<SearchResult.AttrVo> attrVoList = attrIdAgg.getBuckets().stream().map(m -> {
            SearchResult.AttrVo attrVo = new SearchResult.AttrVo();
            try {
                attrVo.setAttrId(Long.valueOf(m.getKeyAsString()));
                Terms attrNameAgg = m.getAggregations().get("attrNameAgg");
                attrVo.setAttrName(attrNameAgg.getBuckets().get(0).getKeyAsString());
                Terms attrValueAgg = attrNameAgg.getBuckets().get(0).getAggregations().get("attrValueAgg");
                List<String> stringList = attrValueAgg.getBuckets().stream().map(MultiBucketsAggregation.Bucket::getKeyAsString).collect(Collectors.toList());
                attrVo.setAttrValue(stringList);
            } catch (NullPointerException e) {
                log.error(e.getMessage(), e);
            }
            return attrVo;
        }).collect(Collectors.toList());
        result.setAttrs(attrVoList);
        return result;
    }
}
